今天來講講 Spring boot 自動配置的總調度者 AutoConfigurationImportSelector(後續簡稱 ACIS)。我們在 main 方法上標記的 @SpringBootApplication,內部組合了一個 @EnableAutoConfiguration,而這個註解的 @Import 裡,匯入的正是 ACIS。ACIS 實作了 DeferredImportSelector 介面,因此它不會在被發現的當下立刻執行,而是等到使用者自己的配置類別都解析完成之後,才開始決定要匯入哪些自動配置類別。簡單來說,ACIS 主要負責回答一個問題:「在上百個候選的自動配置類別裡,這次啟動到底要載入哪些?又要用什麼順序載入?」今天就沿著這個問題,把 ACIS 從讀取清單到交出結果的完整流程走一遍。
ACIS 的核心邏輯集中在 getAutoConfigurationEntry 方法上。方法一開始會先檢查 spring.boot.enableautoconfiguration 這個屬性,如果被設為 false,就會直接回傳空結果,整個自動配置機制也就此關閉;接著呼叫 getCandidateConfigurations 取得候選清單,具體的做法是透過 ImportCandidates 掃描 classpath 上每一個 jar 的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 檔案,把裡面逐行列出的類別名稱全部讀出來。這裡有個小東西可以注意:如果最後一個候選類別都沒有讀到,ACIS 會直接拋出 No auto configuration classes found 的錯誤,這種情況最常發生在自己客製化打包流程的專案上,例如把多個 jar 合併成單一 jar 的時候,同名的 .imports 檔案互相覆蓋,導致清單遺失。讀完之後,ACIS 也會先做一次去重,避免同一個類別因為被多個 jar 重複宣告而被載入兩次。
取得候選清單之後,下一步是處理排除名單。排除的來源一共有 3 種:@SpringBootApplication(或 @EnableAutoConfiguration)的 exclude 屬性、以字串指定類別名稱的 excludeName 屬性、以及設定檔裡的 spring.autoconfigure.exclude。ACIS 會把三者合併成一份排除清單,並在移除之前先做一次檢查:如果某個被排除的類別確實存在於 classpath 上,卻不在候選清單裡,就代表它根本不是一個自動配置類別,這時 ACIS 會直接拋出 IllegalStateException,讓系統在啟動時就失敗。這個檢查主要是用來防呆的,像是把 DataSourceAutoConfiguration 誤寫成 DataSource 這類的錯誤,如果只是默默忽略,開發者就會以為排除已經生效,進而花上許多時間排查預期外的 Bean。
處理完排除名單,ACIS 會呼叫 getConfigurationClassFilter 進行一輪粗篩:由 OnBeanCondition, OnClassCondition, OnWebApplicationCondition 這 3 個 AutoConfigurationImportFilter,搭配編譯期預先生成的 spring-autoconfigure-metadata.properties,在不讀取類別本身的情況下,先淘汰掉明顯不符合條件的候選類別,一般的專案在這一步就會刷掉大半的清單。篩選完成之後,ACIS 還會發布一次 AutoConfigurationImportEvent,通知所有的 AutoConfigurationImportListener。其中最主要的監聽者會把這次的候選清單與排除清單記錄到條件評估報告裡,這也是為什麼我們開啟 debug 模式之後,報告裡除了 Positive matches 和 Negative matches 以外,還會額外列出 Exclusions 和 Unconditional classes 的原因。
最後,是合併與排序的部分:DeferredImportSelector 允許實作類別透過 getImportGroup 指定一個分組,ACIS 回傳的是 AutoConfigurationGroup。ConfigurationClassParser 會把同一組的所有延遲匯入交給同一個 Group 處理:process 負責收集每一個匯入來源的結果,selectImports 則把所有結果合併,統一移除全部的排除類別,再交給 AutoConfigurationSorter 排序。排序一共分成 3 步:先依照類別名稱的字母順序排出一個穩定的基準、再依照 @AutoConfigureOrder 的數值重新排序、最後根據 @AutoConfiguration 的 after / before 屬性做一次拓撲排序,如果配置之間出現互相依賴的循環,就會拋出 AutoConfigure cycle detected 的錯誤。這個順序之所以重要,是因為 @ConditionalOnMissingBean 這類條件只能看到「到目前為止已經註冊的 Bean」,因此一個需要依賴 DataSource 的自動配置,就必須明確宣告排在 DataSourceAutoConfiguration 之後,判斷結果才會穩定。
期待後續各位的閱讀與分享,我是 Pax,我們明天見。